mirror of
https://github.com/n08i40k/polytechnic-android.git
synced 2025-12-06 09:47:48 +03:00
3.1.1
Кешированные ответы от сервера теперь находятся в специальном файле, а не в настройках. Возвращён просмотр подробностей о паре.
This commit is contained in:
6
.idea/kotlinc.xml
generated
6
.idea/kotlinc.xml
generated
@@ -7,10 +7,10 @@
|
||||
<option name="jvmTarget" value="1.8" />
|
||||
</component>
|
||||
<component name="KotlinCommonCompilerArguments">
|
||||
<option name="apiVersion" value="2.0" />
|
||||
<option name="languageVersion" value="2.0" />
|
||||
<option name="apiVersion" value="2.1" />
|
||||
<option name="languageVersion" value="2.1" />
|
||||
</component>
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="2.0.10" />
|
||||
<option name="version" value="2.1.10" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -46,8 +46,8 @@ android {
|
||||
applicationId = "ru.n08i40k.polytechnic.next"
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionCode = 27
|
||||
versionName = "3.1.0"
|
||||
versionCode = 28
|
||||
versionName = "3.1.1"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@@ -158,7 +158,7 @@ dependencies {
|
||||
|
||||
protobuf {
|
||||
protoc {
|
||||
artifact = "com.google.protobuf:protoc:4.29.3"
|
||||
artifact = "com.google.protobuf:protoc:21.0-rc-1"
|
||||
}
|
||||
|
||||
plugins {
|
||||
|
||||
@@ -18,7 +18,8 @@ import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import ru.n08i40k.polytechnic.next.app.AppContainer
|
||||
import ru.n08i40k.polytechnic.next.settings.settings
|
||||
import ru.n08i40k.polytechnic.next.proto.settings
|
||||
import ru.n08i40k.polytechnic.next.proto.settings_v0
|
||||
import ru.n08i40k.polytechnic.next.utils.Observable
|
||||
import ru.n08i40k.polytechnic.next.worker.UpdateFCMTokenWorker
|
||||
import ru.n08i40k.polytechnic.next.worker.UpdateLinkWorker
|
||||
@@ -97,8 +98,35 @@ class Application : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
runBlocking { fixupSettings() }
|
||||
|
||||
VKID.init(this)
|
||||
|
||||
setupFirebase()
|
||||
}
|
||||
|
||||
private suspend fun fixupSettings() {
|
||||
val accessToken = this.settings_v0.data.map { it.accessToken }.first()
|
||||
|
||||
if (accessToken.isEmpty())
|
||||
return
|
||||
|
||||
val userId = this.settings_v0.data.map { it.userId }.first()
|
||||
val group = this.settings_v0.data.map { it.group }.first()
|
||||
val version = this.settings_v0.data.map { it.version }.first()
|
||||
val fcmToken = this.settings_v0.data.map { it.fcmToken }.first()
|
||||
|
||||
this.settings.updateData {
|
||||
it
|
||||
.toBuilder()
|
||||
.setUserId(userId)
|
||||
.setAccessToken(accessToken)
|
||||
.setGroup(group)
|
||||
.setVersion(version)
|
||||
.setFcmToken(fcmToken)
|
||||
.build()
|
||||
}
|
||||
|
||||
this.settings_v0.updateData { it.toBuilder().clear().build() }
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import ru.n08i40k.polytechnic.next.app.NotificationChannels
|
||||
import ru.n08i40k.polytechnic.next.settings.settings
|
||||
import ru.n08i40k.polytechnic.next.proto.settings
|
||||
import ru.n08i40k.polytechnic.next.ui.PolytechnicApp
|
||||
import ru.n08i40k.polytechnic.next.ui.theme.AppTheme
|
||||
import ru.n08i40k.polytechnic.next.utils.app
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.serialization.Serializable
|
||||
import ru.n08i40k.polytechnic.next.R
|
||||
import ru.n08i40k.polytechnic.next.utils.dayMinutes
|
||||
import ru.n08i40k.polytechnic.next.utils.limit
|
||||
|
||||
@Parcelize
|
||||
@@ -17,6 +18,8 @@ data class Lesson(
|
||||
val group: String? = null,
|
||||
val subGroups: List<SubGroup>
|
||||
) : Parcelable {
|
||||
val duration: Int get() = time.end.dayMinutes - time.start.dayMinutes
|
||||
|
||||
fun getShortName(context: Context): String {
|
||||
val name =
|
||||
if (type == LessonType.BREAK)
|
||||
|
||||
@@ -8,7 +8,7 @@ import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import ru.n08i40k.polytechnic.next.app.AppContainer
|
||||
import ru.n08i40k.polytechnic.next.network.RequestBase
|
||||
import ru.n08i40k.polytechnic.next.settings.settings
|
||||
import ru.n08i40k.polytechnic.next.proto.settings
|
||||
|
||||
open class AuthorizedRequest(
|
||||
val appContainer: AppContainer,
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package ru.n08i40k.polytechnic.next.proto
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.CorruptionException
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.core.Serializer
|
||||
import androidx.datastore.dataStore
|
||||
import com.google.protobuf.InvalidProtocolBufferException
|
||||
import ru.n08i40k.polytechnic.next.Cache
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
object CacheSerializer : Serializer<Cache> {
|
||||
override val defaultValue: Cache = Cache.getDefaultInstance()
|
||||
|
||||
override suspend fun readFrom(input: InputStream): Cache =
|
||||
try {
|
||||
Cache.parseFrom(input)
|
||||
} catch (exception: InvalidProtocolBufferException) {
|
||||
throw CorruptionException("Cannot read proto.", exception)
|
||||
}
|
||||
|
||||
override suspend fun writeTo(t: Cache, output: OutputStream) = t.writeTo(output)
|
||||
}
|
||||
|
||||
val Context.cache: DataStore<Cache> by dataStore(
|
||||
fileName = "cache.pb",
|
||||
serializer = CacheSerializer
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
package ru.n08i40k.polytechnic.next.settings
|
||||
package ru.n08i40k.polytechnic.next.proto
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.CorruptionException
|
||||
@@ -23,7 +23,7 @@ object SettingsSerializer : Serializer<Settings> {
|
||||
override suspend fun writeTo(t: Settings, output: OutputStream) = t.writeTo(output)
|
||||
}
|
||||
|
||||
val Context.settings: DataStore<Settings> by dataStore(
|
||||
val Context.settings_v0: DataStore<Settings> by dataStore(
|
||||
fileName = "settings.pb",
|
||||
serializer = SettingsSerializer
|
||||
)
|
||||
@@ -0,0 +1,29 @@
|
||||
package ru.n08i40k.polytechnic.next.proto
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.CorruptionException
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.core.Serializer
|
||||
import androidx.datastore.dataStore
|
||||
import com.google.protobuf.InvalidProtocolBufferException
|
||||
import ru.n08i40k.polytechnic.next.SettingsV2
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
object SettingsV2Serializer : Serializer<SettingsV2> {
|
||||
override val defaultValue: SettingsV2 = SettingsV2.getDefaultInstance()
|
||||
|
||||
override suspend fun readFrom(input: InputStream): SettingsV2 =
|
||||
try {
|
||||
SettingsV2.parseFrom(input)
|
||||
} catch (exception: InvalidProtocolBufferException) {
|
||||
throw CorruptionException("Cannot read proto.", exception)
|
||||
}
|
||||
|
||||
override suspend fun writeTo(t: SettingsV2, output: OutputStream) = t.writeTo(output)
|
||||
}
|
||||
|
||||
val Context.settings: DataStore<SettingsV2> by dataStore(
|
||||
fileName = "settings-v2.pb",
|
||||
serializer = SettingsV2Serializer
|
||||
)
|
||||
@@ -1,12 +1,12 @@
|
||||
package ru.n08i40k.polytechnic.next.repository.cache
|
||||
|
||||
import ru.n08i40k.polytechnic.next.CachedResponse
|
||||
import ru.n08i40k.polytechnic.next.UpdateDates
|
||||
import ru.n08i40k.polytechnic.next.CacheDate
|
||||
import ru.n08i40k.polytechnic.next.CacheResponse
|
||||
|
||||
interface NetworkCacheRepository {
|
||||
suspend fun put(url: String, data: String)
|
||||
|
||||
suspend fun get(url: String): CachedResponse?
|
||||
suspend fun get(url: String): CacheResponse?
|
||||
|
||||
suspend fun clear()
|
||||
|
||||
@@ -14,7 +14,7 @@ interface NetworkCacheRepository {
|
||||
|
||||
suspend fun setHash(hash: String)
|
||||
|
||||
suspend fun getUpdateDates(): UpdateDates
|
||||
suspend fun getUpdateDates(): CacheDate
|
||||
|
||||
suspend fun setUpdateDates(cache: Long, schedule: Long)
|
||||
}
|
||||
@@ -5,17 +5,18 @@ 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.UpdateDates
|
||||
import ru.n08i40k.polytechnic.next.CacheDate
|
||||
import ru.n08i40k.polytechnic.next.CacheResponse
|
||||
import ru.n08i40k.polytechnic.next.app.AppContainer
|
||||
import ru.n08i40k.polytechnic.next.proto.cache
|
||||
import ru.n08i40k.polytechnic.next.repository.cache.NetworkCacheRepository
|
||||
import ru.n08i40k.polytechnic.next.settings.settings
|
||||
import javax.inject.Inject
|
||||
|
||||
class LocalNetworkCacheRepository
|
||||
@Inject constructor(private val appContainer: AppContainer) : NetworkCacheRepository {
|
||||
private val cacheMap: MutableMap<String, CachedResponse> = mutableMapOf()
|
||||
private var updateDates: UpdateDates = UpdateDates.newBuilder().build()
|
||||
private val cacheMap: MutableMap<String, CacheResponse> = mutableMapOf()
|
||||
private var cacheDate: CacheDate = CacheDate.newBuilder().build()
|
||||
|
||||
private var hash: String? = null
|
||||
|
||||
private val context get() = appContainer.context
|
||||
@@ -26,14 +27,14 @@ class LocalNetworkCacheRepository
|
||||
runBlocking {
|
||||
cacheMap.putAll(
|
||||
context
|
||||
.settings
|
||||
.cache
|
||||
.data
|
||||
.map { settings -> settings.cacheStorageMap }.first()
|
||||
.map { it.storageMap }.first()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun get(url: String): CachedResponse? {
|
||||
override suspend fun get(url: String): CacheResponse? {
|
||||
// Если кешированного ответа нет, то возвращаем null
|
||||
// Если хеши не совпадают и локальный хеш присутствует, то возвращаем null
|
||||
|
||||
@@ -49,7 +50,7 @@ class LocalNetworkCacheRepository
|
||||
if (hash == null)
|
||||
throw IllegalStateException("Не установлен хеш!")
|
||||
|
||||
cacheMap[url] = CachedResponse
|
||||
cacheMap[url] = CacheResponse
|
||||
.newBuilder()
|
||||
.setHash(this.hash)
|
||||
.setData(data)
|
||||
@@ -83,21 +84,21 @@ class LocalNetworkCacheRepository
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getUpdateDates(): UpdateDates {
|
||||
return this.updateDates
|
||||
override suspend fun getUpdateDates(): CacheDate {
|
||||
return this.cacheDate
|
||||
}
|
||||
|
||||
override suspend fun setUpdateDates(cache: Long, schedule: Long) {
|
||||
updateDates = UpdateDates
|
||||
cacheDate = CacheDate
|
||||
.newBuilder()
|
||||
.setCache(cache)
|
||||
.setSchedule(schedule).build()
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
context.settings.updateData {
|
||||
context.cache.updateData {
|
||||
it
|
||||
.toBuilder()
|
||||
.setUpdateDates(updateDates)
|
||||
.setDate(cacheDate)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
@@ -106,10 +107,10 @@ class LocalNetworkCacheRepository
|
||||
|
||||
private suspend fun save() {
|
||||
withContext(Dispatchers.IO) {
|
||||
context.settings.updateData {
|
||||
context.cache.updateData {
|
||||
it
|
||||
.toBuilder()
|
||||
.putAllCacheStorage(cacheMap)
|
||||
.putAllStorage(cacheMap)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package ru.n08i40k.polytechnic.next.repository.cache.impl
|
||||
|
||||
import ru.n08i40k.polytechnic.next.CachedResponse
|
||||
import ru.n08i40k.polytechnic.next.UpdateDates
|
||||
import ru.n08i40k.polytechnic.next.CacheDate
|
||||
import ru.n08i40k.polytechnic.next.CacheResponse
|
||||
import ru.n08i40k.polytechnic.next.repository.cache.NetworkCacheRepository
|
||||
|
||||
class MockNetworkCacheRepository : NetworkCacheRepository {
|
||||
override suspend fun get(url: String): CachedResponse? {
|
||||
override suspend fun get(url: String): CacheResponse? {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ class MockNetworkCacheRepository : NetworkCacheRepository {
|
||||
|
||||
override suspend fun setHash(hash: String) {}
|
||||
|
||||
override suspend fun getUpdateDates(): UpdateDates {
|
||||
return UpdateDates.newBuilder().build()
|
||||
override suspend fun getUpdateDates(): CacheDate {
|
||||
return CacheDate.newBuilder().build()
|
||||
}
|
||||
|
||||
override suspend fun setUpdateDates(cache: Long, schedule: Long) {}
|
||||
|
||||
@@ -7,8 +7,9 @@ import ru.n08i40k.polytechnic.next.model.Profile
|
||||
import ru.n08i40k.polytechnic.next.network.request.fcm.FcmSetToken
|
||||
import ru.n08i40k.polytechnic.next.network.request.profile.ProfileMe
|
||||
import ru.n08i40k.polytechnic.next.network.tryFuture
|
||||
import ru.n08i40k.polytechnic.next.proto.cache
|
||||
import ru.n08i40k.polytechnic.next.proto.settings
|
||||
import ru.n08i40k.polytechnic.next.repository.profile.ProfileRepository
|
||||
import ru.n08i40k.polytechnic.next.settings.settings
|
||||
import ru.n08i40k.polytechnic.next.utils.MyResult
|
||||
import ru.n08i40k.polytechnic.next.utils.app
|
||||
|
||||
@@ -40,7 +41,14 @@ class RemoteProfileRepository(private val container: AppContainer) : ProfileRepo
|
||||
override suspend fun signOut() {
|
||||
val context = container.context
|
||||
|
||||
container.context.settings.updateData {
|
||||
context.settings.updateData {
|
||||
it
|
||||
.toBuilder()
|
||||
.clear()
|
||||
.build()
|
||||
}
|
||||
|
||||
context.cache.updateData {
|
||||
it
|
||||
.toBuilder()
|
||||
.clear()
|
||||
@@ -48,12 +56,5 @@ class RemoteProfileRepository(private val container: AppContainer) : ProfileRepo
|
||||
}
|
||||
|
||||
context.app.events.signOut.next(Unit)
|
||||
|
||||
// context.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)
|
||||
// val pm = context.packageManager
|
||||
// val intent = pm.getLaunchIntentForPackage(context.packageName)
|
||||
// val mainIntent = Intent.makeRestartActivityTask(intent?.component)
|
||||
// context.startActivity(mainIntent)
|
||||
// Runtime.getRuntime().exit(0)
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import ru.n08i40k.polytechnic.next.Application
|
||||
import ru.n08i40k.polytechnic.next.R
|
||||
import ru.n08i40k.polytechnic.next.settings.settings
|
||||
import ru.n08i40k.polytechnic.next.proto.settings
|
||||
import ru.n08i40k.polytechnic.next.ui.screen.MainScreen
|
||||
import ru.n08i40k.polytechnic.next.ui.screen.auth.AuthScreen
|
||||
import ru.n08i40k.polytechnic.next.utils.app
|
||||
|
||||
@@ -9,7 +9,7 @@ import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import ru.n08i40k.polytechnic.next.UpdateDates
|
||||
import ru.n08i40k.polytechnic.next.CacheDate
|
||||
import ru.n08i40k.polytechnic.next.app.AppContainer
|
||||
import ru.n08i40k.polytechnic.next.model.GroupOrTeacher
|
||||
import ru.n08i40k.polytechnic.next.utils.MyResult
|
||||
@@ -25,7 +25,7 @@ sealed interface GroupUiState {
|
||||
|
||||
data class HasData(
|
||||
val group: GroupOrTeacher,
|
||||
val updateDates: UpdateDates,
|
||||
val cacheDate: CacheDate,
|
||||
val lastUpdateAt: Long,
|
||||
override val isLoading: Boolean
|
||||
) : GroupUiState
|
||||
@@ -33,7 +33,7 @@ sealed interface GroupUiState {
|
||||
|
||||
private data class GroupViewModelState(
|
||||
val group: GroupOrTeacher? = null,
|
||||
val updateDates: UpdateDates? = null,
|
||||
val updateDates: CacheDate? = null,
|
||||
val lastUpdateAt: Long = 0,
|
||||
val isLoading: Boolean = false
|
||||
) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import ru.n08i40k.polytechnic.next.UpdateDates
|
||||
import ru.n08i40k.polytechnic.next.CacheDate
|
||||
import ru.n08i40k.polytechnic.next.app.AppContainer
|
||||
import ru.n08i40k.polytechnic.next.model.GroupOrTeacher
|
||||
import ru.n08i40k.polytechnic.next.utils.MyResult
|
||||
@@ -25,7 +25,7 @@ sealed interface SearchUiState {
|
||||
|
||||
data class HasData(
|
||||
val teacher: GroupOrTeacher,
|
||||
val updateDates: UpdateDates,
|
||||
val cacheDate: CacheDate,
|
||||
val lastUpdateAt: Long,
|
||||
override val isLoading: Boolean
|
||||
) : SearchUiState
|
||||
@@ -33,7 +33,7 @@ sealed interface SearchUiState {
|
||||
|
||||
private data class SearchViewModelState(
|
||||
val teacher: GroupOrTeacher? = null,
|
||||
val updateDates: UpdateDates? = null,
|
||||
val updateDates: CacheDate? = null,
|
||||
val lastUpdateAt: Long = 0,
|
||||
val isLoading: Boolean = false
|
||||
) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import ru.n08i40k.polytechnic.next.UpdateDates
|
||||
import ru.n08i40k.polytechnic.next.CacheDate
|
||||
import ru.n08i40k.polytechnic.next.app.AppContainer
|
||||
import ru.n08i40k.polytechnic.next.model.GroupOrTeacher
|
||||
import ru.n08i40k.polytechnic.next.utils.MyResult
|
||||
@@ -24,7 +24,7 @@ sealed interface TeacherUiState {
|
||||
|
||||
data class HasData(
|
||||
val teacher: GroupOrTeacher,
|
||||
val updateDates: UpdateDates,
|
||||
val cacheDate: CacheDate,
|
||||
val lastUpdateAt: Long,
|
||||
override val isLoading: Boolean
|
||||
) : TeacherUiState
|
||||
@@ -32,7 +32,7 @@ sealed interface TeacherUiState {
|
||||
|
||||
private data class TeacherViewModelState(
|
||||
val teacher: GroupOrTeacher? = null,
|
||||
val updateDates: UpdateDates? = null,
|
||||
val updateDates: CacheDate? = null,
|
||||
val lastUpdateAt: Long = 0,
|
||||
val isLoading: Boolean = false
|
||||
) {
|
||||
|
||||
@@ -38,7 +38,7 @@ import kotlinx.coroutines.runBlocking
|
||||
import ru.n08i40k.polytechnic.next.Application
|
||||
import ru.n08i40k.polytechnic.next.R
|
||||
import ru.n08i40k.polytechnic.next.model.UserRole
|
||||
import ru.n08i40k.polytechnic.next.settings.settings
|
||||
import ru.n08i40k.polytechnic.next.proto.settings
|
||||
import ru.n08i40k.polytechnic.next.ui.AppRoute
|
||||
import ru.n08i40k.polytechnic.next.ui.icons.AppIcons
|
||||
import ru.n08i40k.polytechnic.next.ui.icons.appicons.Filled
|
||||
|
||||
@@ -35,6 +35,8 @@ import androidx.navigation.compose.rememberNavController
|
||||
import com.google.android.gms.tasks.OnCompleteListener
|
||||
import com.google.android.gms.tasks.Task
|
||||
import com.google.firebase.messaging.FirebaseMessaging
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import ru.n08i40k.polytechnic.next.proto.cache
|
||||
import ru.n08i40k.polytechnic.next.ui.AppRoute
|
||||
import ru.n08i40k.polytechnic.next.ui.helper.PushSnackbar
|
||||
import ru.n08i40k.polytechnic.next.ui.helper.SnackbarBox
|
||||
@@ -61,8 +63,7 @@ private fun FormWrapper(
|
||||
with(localDensity) {
|
||||
onWidthChange(it.size.width.toDp())
|
||||
}
|
||||
}
|
||||
/*.animateContentSize()*/,
|
||||
},
|
||||
content = content
|
||||
)
|
||||
}
|
||||
@@ -84,6 +85,8 @@ private fun AuthForm(parentNavController: NavController, pushSnackbar: PushSnack
|
||||
}
|
||||
|
||||
val finish: () -> Unit = {
|
||||
runBlocking { context.cache.updateData { it.toBuilder().clear().build() } }
|
||||
|
||||
parentNavController.navigate(AppRoute.MAIN.route) {
|
||||
popUpTo(AppRoute.AUTH.route) { inclusive = true }
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ import kotlinx.coroutines.runBlocking
|
||||
import ru.n08i40k.polytechnic.next.R
|
||||
import ru.n08i40k.polytechnic.next.network.request.auth.AuthSignIn
|
||||
import ru.n08i40k.polytechnic.next.network.unwrapException
|
||||
import ru.n08i40k.polytechnic.next.settings.settings
|
||||
import ru.n08i40k.polytechnic.next.proto.settings
|
||||
import ru.n08i40k.polytechnic.next.ui.helper.PushSnackbar
|
||||
import ru.n08i40k.polytechnic.next.ui.helper.data.rememberInputValue
|
||||
import java.util.logging.Logger
|
||||
|
||||
@@ -8,7 +8,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import ru.n08i40k.polytechnic.next.network.request.auth.AuthSignInVK
|
||||
import ru.n08i40k.polytechnic.next.network.unwrapException
|
||||
import ru.n08i40k.polytechnic.next.settings.settings
|
||||
import ru.n08i40k.polytechnic.next.proto.settings
|
||||
import ru.n08i40k.polytechnic.next.ui.widgets.OneTapComplete
|
||||
import java.util.logging.Logger
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ import ru.n08i40k.polytechnic.next.R
|
||||
import ru.n08i40k.polytechnic.next.model.UserRole
|
||||
import ru.n08i40k.polytechnic.next.network.request.auth.AuthSignUp
|
||||
import ru.n08i40k.polytechnic.next.network.unwrapException
|
||||
import ru.n08i40k.polytechnic.next.settings.settings
|
||||
import ru.n08i40k.polytechnic.next.proto.settings
|
||||
import ru.n08i40k.polytechnic.next.ui.helper.PushSnackbar
|
||||
import ru.n08i40k.polytechnic.next.ui.helper.data.rememberInputValue
|
||||
import ru.n08i40k.polytechnic.next.ui.widgets.selector.GroupSelector
|
||||
|
||||
@@ -41,7 +41,7 @@ import ru.n08i40k.polytechnic.next.R
|
||||
import ru.n08i40k.polytechnic.next.model.UserRole
|
||||
import ru.n08i40k.polytechnic.next.network.request.auth.AuthSignUpVK
|
||||
import ru.n08i40k.polytechnic.next.network.unwrapException
|
||||
import ru.n08i40k.polytechnic.next.settings.settings
|
||||
import ru.n08i40k.polytechnic.next.proto.settings
|
||||
import ru.n08i40k.polytechnic.next.ui.helper.PushSnackbar
|
||||
import ru.n08i40k.polytechnic.next.ui.helper.data.rememberInputValue
|
||||
import ru.n08i40k.polytechnic.next.ui.widgets.selector.GroupSelector
|
||||
|
||||
@@ -65,7 +65,7 @@ fun GroupScheduleScreen(viewModel: GroupViewModel) {
|
||||
Column {
|
||||
val data = uiState as GroupUiState.HasData
|
||||
|
||||
UpdateInfo(data.lastUpdateAt, data.updateDates)
|
||||
UpdateInfo(data.lastUpdateAt, data.cacheDate)
|
||||
Spacer(Modifier.height(10.dp))
|
||||
SchedulePager(data.group)
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ fun TeacherScheduleScreen(viewModel: TeacherViewModel) {
|
||||
Column {
|
||||
val data = uiState as TeacherUiState.HasData
|
||||
|
||||
UpdateInfo(data.lastUpdateAt, data.updateDates)
|
||||
UpdateInfo(data.lastUpdateAt, data.cacheDate)
|
||||
Spacer(Modifier.height(10.dp))
|
||||
SchedulePager(data.teacher)
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ fun TeacherSearchScreen(viewModel: SearchViewModel) {
|
||||
Column {
|
||||
val data = uiState as SearchUiState.HasData
|
||||
|
||||
UpdateInfo(data.lastUpdateAt, data.updateDates)
|
||||
UpdateInfo(data.lastUpdateAt, data.cacheDate)
|
||||
Spacer(Modifier.height(10.dp))
|
||||
SchedulePager(data.teacher)
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import ru.n08i40k.polytechnic.next.CacheDate
|
||||
import ru.n08i40k.polytechnic.next.R
|
||||
import ru.n08i40k.polytechnic.next.UpdateDates
|
||||
import ru.n08i40k.polytechnic.next.ui.widgets.ExpandableCard
|
||||
import ru.n08i40k.polytechnic.next.ui.widgets.ExpandableCardTitle
|
||||
import ru.n08i40k.polytechnic.next.utils.*
|
||||
@@ -32,15 +32,15 @@ val expanded = mutableStateOf(false)
|
||||
@Composable
|
||||
fun UpdateInfo(
|
||||
lastUpdateAt: Long = 0,
|
||||
updateDates: UpdateDates = UpdateDates.newBuilder().build()
|
||||
cacheDate: CacheDate = CacheDate.newBuilder().build()
|
||||
) {
|
||||
var expanded by remember { expanded }
|
||||
|
||||
val format = "HH:mm:ss dd.MM.yyyy"
|
||||
|
||||
val currentDate = Date(lastUpdateAt).toString(format)
|
||||
val cacheUpdateDate = Date(updateDates.cache).toString(format)
|
||||
val scheduleUpdateDate = Date(updateDates.schedule).toString(format)
|
||||
val cacheUpdateDate = Date(cacheDate.cache).toString(format)
|
||||
val scheduleUpdateDate = Date(cacheDate.schedule).toString(format)
|
||||
|
||||
ExpandableCard(
|
||||
expanded = expanded,
|
||||
|
||||
@@ -18,9 +18,7 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
@@ -34,6 +32,7 @@ import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import ru.n08i40k.polytechnic.next.R
|
||||
import ru.n08i40k.polytechnic.next.model.Day
|
||||
import ru.n08i40k.polytechnic.next.model.Lesson
|
||||
import ru.n08i40k.polytechnic.next.model.LessonType
|
||||
import ru.n08i40k.polytechnic.next.repository.schedule.impl.MockScheduleRepository
|
||||
import ru.n08i40k.polytechnic.next.utils.dateTime
|
||||
@@ -80,7 +79,8 @@ private fun getCurrentLessonIdx(day: Day?): Flow<Int> {
|
||||
@Composable
|
||||
fun DayCard(
|
||||
modifier: Modifier = Modifier,
|
||||
day: Day = MockScheduleRepository.exampleTeacher.days[0]
|
||||
day: Day = MockScheduleRepository.exampleTeacher.days[0],
|
||||
onLessonClick: (Lesson) -> Unit = {},
|
||||
) {
|
||||
val offset = remember(day) { getDayOffset(day) }
|
||||
|
||||
@@ -119,9 +119,9 @@ fun DayCard(
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
|
||||
if (day.street != null) {
|
||||
day.street?.let {
|
||||
Text(
|
||||
day.street,
|
||||
it,
|
||||
Modifier.fillMaxWidth(),
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
@@ -177,12 +177,9 @@ fun DayCard(
|
||||
LessonType.EXAM_DEFAULT -> examCardColors
|
||||
}
|
||||
|
||||
// TODO: Вернуть ExtraInfo
|
||||
var extraInfo by remember { mutableStateOf(false) }
|
||||
|
||||
Box(
|
||||
Modifier
|
||||
.clickable { extraInfo = true }
|
||||
.clickable { onLessonClick(lesson) }
|
||||
.background(cardColors.containerColor)
|
||||
) {
|
||||
val modifier =
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
package ru.n08i40k.polytechnic.next.ui.widgets.schedule
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Card
|
||||
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.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.max
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import ru.n08i40k.polytechnic.next.R
|
||||
import ru.n08i40k.polytechnic.next.model.Lesson
|
||||
import ru.n08i40k.polytechnic.next.model.LessonType
|
||||
import ru.n08i40k.polytechnic.next.repository.schedule.impl.MockScheduleRepository
|
||||
import ru.n08i40k.polytechnic.next.utils.dayMinutes
|
||||
import ru.n08i40k.polytechnic.next.utils.fmtAsClock
|
||||
|
||||
class LessonPreviewParameterProvider : PreviewParameterProvider<Lesson> {
|
||||
override val values: Sequence<Lesson>
|
||||
get() {
|
||||
val lessons = MockScheduleRepository.exampleGroup.days[0].lessons
|
||||
|
||||
return sequenceOf(
|
||||
lessons[0],
|
||||
lessons[2],
|
||||
lessons[4],
|
||||
lessons[6],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun ExtraInfoDialogPreview(
|
||||
@PreviewParameter(LessonPreviewParameterProvider::class) lesson: Lesson
|
||||
) {
|
||||
ExtraInfoDialog(lesson) { }
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ExtraInfoDialog(
|
||||
lesson: Lesson,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
Dialog(onDismiss) {
|
||||
Card {
|
||||
Column(Modifier.padding(10.dp)) {
|
||||
var minWidth by remember { mutableStateOf(Dp.Unspecified) }
|
||||
val density = LocalDensity.current
|
||||
|
||||
@Composable
|
||||
fun kvText(title: String, text: String) {
|
||||
Row(
|
||||
Modifier.alpha(if (minWidth == Dp.Unspecified) 0f else 1f),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
title,
|
||||
Modifier
|
||||
.onGloballyPositioned {
|
||||
with(density) {
|
||||
val dp = it.size.width.toDp()
|
||||
|
||||
minWidth =
|
||||
if (minWidth == Dp.Unspecified)
|
||||
dp
|
||||
else
|
||||
max(minWidth, dp)
|
||||
}
|
||||
}
|
||||
.size(minWidth, Dp.Unspecified),
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
textAlign = TextAlign.Right,
|
||||
)
|
||||
|
||||
Text(text)
|
||||
}
|
||||
}
|
||||
|
||||
kvText(stringResource(R.string.extra_info_lesson_name), lesson.name ?: "")
|
||||
when (lesson.type) {
|
||||
LessonType.BREAK -> throw IllegalArgumentException()
|
||||
LessonType.DEFAULT -> null
|
||||
LessonType.ADDITIONAL -> null
|
||||
LessonType.CONSULTATION -> R.string.lesson_type_consultation
|
||||
LessonType.INDEPENDENT_WORK -> R.string.lesson_type_independent_work
|
||||
LessonType.EXAM -> R.string.lesson_type_exam
|
||||
LessonType.EXAM_WITH_GRADE -> R.string.lesson_type_exam_with_grade
|
||||
LessonType.EXAM_DEFAULT -> R.string.lesson_type_exam_default
|
||||
}?.let {
|
||||
kvText(stringResource(R.string.extra_info_type), stringResource(it))
|
||||
}
|
||||
|
||||
if (lesson.subGroups.size == 1) {
|
||||
kvText(
|
||||
stringResource(R.string.extra_info_teacher),
|
||||
stringResource(
|
||||
R.string.extra_info_teacher_second,
|
||||
lesson.subGroups[0].teacher,
|
||||
lesson.subGroups[0].cabinet
|
||||
)
|
||||
)
|
||||
} else {
|
||||
for (subGroup in lesson.subGroups) {
|
||||
kvText(
|
||||
stringResource(R.string.extra_info_teacher),
|
||||
stringResource(
|
||||
R.string.extra_info_teacher_second_subgroup,
|
||||
subGroup.teacher,
|
||||
subGroup.cabinet,
|
||||
subGroup.number
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
kvText(
|
||||
stringResource(R.string.extra_info_duration),
|
||||
stringResource(
|
||||
R.string.extra_info_duration_second,
|
||||
lesson.time.start.dayMinutes.fmtAsClock(),
|
||||
lesson.time.end.dayMinutes.fmtAsClock(),
|
||||
lesson.duration / 60,
|
||||
lesson.duration % 60
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,10 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
@@ -17,10 +21,13 @@ import androidx.compose.ui.util.lerp
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import ru.n08i40k.polytechnic.next.R
|
||||
import ru.n08i40k.polytechnic.next.model.GroupOrTeacher
|
||||
import ru.n08i40k.polytechnic.next.model.Lesson
|
||||
import ru.n08i40k.polytechnic.next.model.LessonType
|
||||
import ru.n08i40k.polytechnic.next.repository.schedule.impl.MockScheduleRepository
|
||||
import ru.n08i40k.polytechnic.next.ui.widgets.NotificationCard
|
||||
import ru.n08i40k.polytechnic.next.utils.dateTime
|
||||
import ru.n08i40k.polytechnic.next.utils.now
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.logging.Level
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
@@ -40,6 +47,8 @@ fun SchedulePager(schedule: GroupOrTeacher = MockScheduleRepository.exampleTeach
|
||||
pageCount = { schedule.days.size }
|
||||
)
|
||||
|
||||
var dialogLesson by remember { mutableStateOf<WeakReference<Lesson>?>(null) }
|
||||
|
||||
Column {
|
||||
if (isScheduleOutdated(schedule))
|
||||
NotificationCard(Level.WARNING, stringResource(R.string.outdated_schedule))
|
||||
@@ -69,7 +78,14 @@ fun SchedulePager(schedule: GroupOrTeacher = MockScheduleRepository.exampleTeach
|
||||
)
|
||||
},
|
||||
schedule.days[page]
|
||||
)
|
||||
) { dialogLesson = WeakReference(it) }
|
||||
}
|
||||
}
|
||||
|
||||
dialogLesson?.get()?.let { lesson ->
|
||||
if (lesson.type == LessonType.BREAK)
|
||||
return@let
|
||||
|
||||
ExtraInfoDialog(lesson) { dialogLesson = null }
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import ru.n08i40k.polytechnic.next.app.appContainer
|
||||
import ru.n08i40k.polytechnic.next.settings.settings
|
||||
import ru.n08i40k.polytechnic.next.proto.settings
|
||||
import ru.n08i40k.polytechnic.next.utils.MyResult
|
||||
import java.time.Duration
|
||||
import java.util.logging.Logger
|
||||
|
||||
@@ -8,7 +8,7 @@ import androidx.work.WorkerParameters
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import ru.n08i40k.polytechnic.next.app.appContainer
|
||||
import ru.n08i40k.polytechnic.next.settings.settings
|
||||
import ru.n08i40k.polytechnic.next.proto.settings
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.logging.Logger
|
||||
|
||||
|
||||
19
app/src/main/proto/cache.proto
Normal file
19
app/src/main/proto/cache.proto
Normal file
@@ -0,0 +1,19 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option java_package = "ru.n08i40k.polytechnic.next";
|
||||
option java_multiple_files = true;
|
||||
|
||||
message CacheResponse {
|
||||
string hash = 1;
|
||||
string data = 2;
|
||||
}
|
||||
|
||||
message CacheDate {
|
||||
int64 cache = 1;
|
||||
int64 schedule = 2;
|
||||
}
|
||||
|
||||
message Cache {
|
||||
map<string, CacheResponse> storage = 4;
|
||||
CacheDate date = 5;
|
||||
}
|
||||
15
app/src/main/proto/settings-v2.proto
Normal file
15
app/src/main/proto/settings-v2.proto
Normal file
@@ -0,0 +1,15 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option java_package = "ru.n08i40k.polytechnic.next";
|
||||
option java_multiple_files = true;
|
||||
|
||||
message SettingsV2 {
|
||||
string user_id = 1;
|
||||
string access_token = 2;
|
||||
string group = 3;
|
||||
|
||||
string version = 5;
|
||||
string suppressed_version = 6;
|
||||
|
||||
string fcm_token = 7;
|
||||
}
|
||||
@@ -100,4 +100,11 @@
|
||||
<string name="day_view_end_description">Ура, можно идти домой! Наверное :(</string>
|
||||
<string name="day_view_channel_name">Текущая пара</string>
|
||||
<string name="day_view_channel_description">Отображает текущую пару или перемену в уведомлении</string>
|
||||
<string name="extra_info_lesson_name">"Название: "</string>
|
||||
<string name="extra_info_teacher">"Преподаватель: "</string>
|
||||
<string name="extra_info_teacher_second">%1$s в %2$s каб.</string>
|
||||
<string name="extra_info_teacher_second_subgroup">%1$s в %2$s каб. [подгруппа - %3$d]</string>
|
||||
<string name="extra_info_duration">"Длительность: "</string>
|
||||
<string name="extra_info_duration_second">С %1$s до %2$s (%3$d ч. %4$d мин.)</string>
|
||||
<string name="extra_info_type">"Тип: "</string>
|
||||
</resources>
|
||||
@@ -100,4 +100,11 @@
|
||||
<string name="day_view_end_description">ya ne budu eto perevidit\'</string>
|
||||
<string name="day_view_channel_name">Current lesson</string>
|
||||
<string name="day_view_channel_description">View the current lesson and breaks in notification</string>
|
||||
<string name="extra_info_lesson_name">"Name: "</string>
|
||||
<string name="extra_info_teacher">"Teacher: "</string>
|
||||
<string name="extra_info_teacher_second">%1$s in %2$s cab.</string>
|
||||
<string name="extra_info_teacher_second_subgroup">%1$s in %2$s cab. [subgroup - %3$d]</string>
|
||||
<string name="extra_info_duration">"Duration: "</string>
|
||||
<string name="extra_info_duration_second">From %1$s to %2$s (%3$d h. %4$d min.)</string>
|
||||
<string name="extra_info_type">"Type: "</string>
|
||||
</resources>
|
||||
@@ -1,27 +1,28 @@
|
||||
[versions]
|
||||
agp = "8.8.1"
|
||||
desugar_jdk_libs = "2.1.4"
|
||||
agp = "8.8.2"
|
||||
desugar_jdk_libs = "2.1.5"
|
||||
kotlin = "2.1.10"
|
||||
coreKtx = "1.15.0"
|
||||
junit = "4.13.2"
|
||||
junitVersion = "1.2.1"
|
||||
espressoCore = "3.6.1"
|
||||
lifecycleRuntimeKtx = "2.8.7"
|
||||
activityCompose = "1.10.0"
|
||||
composeBom = "2025.02.00"
|
||||
activityCompose = "1.10.1"
|
||||
composeBom = "2025.03.00"
|
||||
accompanistSwiperefresh = "0.36.0"
|
||||
firebaseBom = "33.9.0"
|
||||
firebaseBom = "33.10.0"
|
||||
hiltAndroid = "2.55"
|
||||
hiltAndroidCompiler = "2.55"
|
||||
hiltNavigationCompose = "1.2.0"
|
||||
kotlinxSerializationJson = "1.8.0"
|
||||
protobufLite = "3.0.1"
|
||||
volley = "1.2.1"
|
||||
datastore = "1.1.2"
|
||||
datastore = "1.1.3"
|
||||
navigationCompose = "2.8.9"
|
||||
googleFirebaseCrashlytics = "3.0.3"
|
||||
workRuntime = "2.10.0"
|
||||
vkid = "2.3.1"
|
||||
#noinspection GradleDependency
|
||||
vkid = "2.2.2"
|
||||
|
||||
[libraries]
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
@@ -59,8 +60,8 @@ volley = { group = "com.android.volley", name = "volley", version.ref = "volley"
|
||||
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
|
||||
|
||||
firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" }
|
||||
firebase-analytics = { module = "com.google.firebase:firebase-analytics", version = "22.2.0" }
|
||||
firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics", version = "19.4.0" }
|
||||
firebase-analytics = { module = "com.google.firebase:firebase-analytics", version = "22.3.0" }
|
||||
firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics", version = "19.4.1" }
|
||||
firebase-messaging = { group = "com.google.firebase", name = "firebase-messaging", version = "24.1.0" }
|
||||
firebase-config = { group = "com.google.firebase", name = "firebase-config", version = "22.1.0" }
|
||||
vk-vkid = {group = "com.vk.id", name = "vkid", version.ref = "vkid" }
|
||||
|
||||
Reference in New Issue
Block a user